package com.atguigu.tingshu.search.service.impl;
import cn.hutool.core.util.PageUtil;
import cn.hutool.core.util.StrUtil;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldSort;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.*;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.*;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.common.util.PinYinUtils;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.model.user.UserInfo;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumIndexRepository;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.search.repository.SuggestIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.album.AlbumStatVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexSearchVo;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.suggest.Completion;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {

    private final AlbumIndexRepository albumIndexRepository ;

    private final AlbumInfoFeignClient albumInfoFeignClient;

    private final CategoryFeignClient categoryFeignClient;

    private final UserInfoFeignClient userInfoFeignClient;

    private final ElasticsearchClient elasticsearchClient;

    private final SuggestIndexRepository suggestIndexRepository;

    private final RedisTemplate<String,String> redisTemplate;

    private final RedissonClient redissonClient;

    @Autowired
    public SearchServiceImpl(AlbumIndexRepository albumIndexRepository, AlbumInfoFeignClient albumInfoFeignClient, CategoryFeignClient categoryFeignClient, UserInfoFeignClient userInfoFeignClient, ElasticsearchClient elasticsearchClient, SuggestIndexRepository suggestIndexRepository, RedisTemplate<String, String> redisTemplate, RedissonClient redissonClient) {
        this.albumIndexRepository = albumIndexRepository;
        this.albumInfoFeignClient = albumInfoFeignClient;
        this.categoryFeignClient = categoryFeignClient;
        this.userInfoFeignClient = userInfoFeignClient;
        this.elasticsearchClient = elasticsearchClient;
        this.suggestIndexRepository = suggestIndexRepository;
        this.redisTemplate = redisTemplate;
        this.redissonClient = redissonClient;
    }

    /**
     * @description: 上架专辑
     * @author Petrichor
     * @date 2023/12/26 18:48
     * @param albumId 专辑id
     * @return NULL
     **/
    @Override
    public void upperAlbum(Long albumId) {
        // 创建AlbumInfoIndex对象，封装要保存到ES索引库中的数据
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex() ;
        // 1、TODO：远程调用service-album微服务接口---根据专辑id获取专辑数据
        CompletableFuture<AlbumInfo> albumInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {

            // album_info
            Result<AlbumInfo> albumInfoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            AlbumInfo albumInfo = albumInfoResult.getData();
            BeanUtils.copyProperties(albumInfo, albumInfoIndex);
            albumInfoIndex.setIsFinished(String.valueOf(albumInfo.getIsFinished()));

            System.out.println(albumInfoIndex);
            return albumInfo;

        });
        // 2、TODO：远程调用service-album微服务接口---根据专辑id获取专辑属性数据
        CompletableFuture<Void> albumAttributeValueCompletableFuture = CompletableFuture.runAsync(() -> {

            // album_atribute_value
            Result<List<AlbumAttributeValue>> albumAttributeValueByAlbumId = albumInfoFeignClient.findAlbumAttributeValueByAlbumId(albumId);
            List<AlbumAttributeValue> attributeValueList = albumAttributeValueByAlbumId.getData();
            List<AttributeValueIndex> attributeValueIndexList = attributeValueList.stream().map(albumAttributeValue -> {
                AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                attributeValueIndex.setValueId(albumAttributeValue.getValueId());
                return attributeValueIndex;
            }).collect(Collectors.toList());
            albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);

        });
        // 3、TODO：远程调用service-album微服务接口---根据三级分类id查询分类数据
        CompletableFuture<Void> baseCategoryVoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync((albumInfo) -> {

            // album_stat
            Result<BaseCategoryView> categoryViewResult = categoryFeignClient.findCategoryByC3Id(albumInfo.getCategory3Id());
            BaseCategoryView categoryView = categoryViewResult.getData();
            BeanUtils.copyProperties(categoryView, albumInfoIndex , "id");

        });
        // 4、TODO：远程调用service-user微服务接口---根据用户Id获取到用户信息
        CompletableFuture<Void> useInfoCompletableFuture = albumInfoCompletableFuture.thenAcceptAsync((albumInfo) -> {

            Result<UserInfo> userInfoResult = userInfoFeignClient.findUserInfoByUserId(albumInfo.getUserId());
            UserInfo userInfo = userInfoResult.getData();
            albumInfoIndex.setAnnouncerName(userInfo.getNickname());
        });
        // 5. TODO: 根据专辑的id查询专辑的统计数据
        CompletableFuture<Void> otherCompletableFuture = CompletableFuture.runAsync(() -> {

            // 专辑的统计数据
            Result<AlbumStatVo> albumStatVoResult = albumInfoFeignClient.findAlbumStatByAlbumId(albumId);
            AlbumStatVo albumStatVo = albumStatVoResult.getData();
            BeanUtils.copyProperties(albumStatVo, albumInfoIndex , "id");
            // 热度分表示的就是专辑的受欢迎程度
            double hotScore = albumStatVo.getPlayStatNum() * 0.2
                    + albumStatVo.getSubscribeStatNum() * 0.3
                    + albumStatVo.getBuyStatNum() * 0.4
                    + albumStatVo.getCommentStatNum() * 0.1;
            albumInfoIndex.setHotScore(hotScore);
        });

        // 7、保存数据到ES索引库中
        CompletableFuture.allOf(albumInfoCompletableFuture ,
                albumAttributeValueCompletableFuture ,
                baseCategoryVoCompletableFuture ,
                useInfoCompletableFuture ,
                otherCompletableFuture).join() ;

        albumIndexRepository.save(albumInfoIndex) ;

        // 6. TODO:初始化提词库
        CompletableFuture.runAsync(()->{
            initSuggestIndexRepository(albumInfoIndex);
        });
        // 将专辑的id保存到布隆过滤器中
        RBloomFilter<Object> bloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER);
        bloomFilter.add(albumId) ;

    }
    /**
     * @description: 初始化提词库 方法
     * @author Petrichor
     * @date 2023/12/28 19:47
     * @param
     * @return
    **/
        private void initSuggestIndexRepository(AlbumInfoIndex albumInfoIndex) {

            // 把专辑的标题、简介、主播名称存储到自动补全的索引库中
            SuggestIndex albumTitleSuggestIndex = new SuggestIndex() ;
            albumTitleSuggestIndex.setId(UUID.randomUUID().toString().replace("-" , ""));
            albumTitleSuggestIndex.setTitle(albumInfoIndex.getAlbumTitle());
            albumTitleSuggestIndex.setKeyword(new Completion(new String[]{ albumInfoIndex.getAlbumTitle() }));

            // 把汉语转换成拼音需要使用： pinyin4j
            albumTitleSuggestIndex.setKeywordPinyin(new Completion(new String[]{ PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumTitle()) } ));
            albumTitleSuggestIndex.setKeywordSequence(new Completion(new String[]{ PinYinUtils.getFirstLetter( albumInfoIndex.getAlbumTitle()) } ));
            suggestIndexRepository.save(albumTitleSuggestIndex) ;


            // 专辑的简介
            SuggestIndex albumIntroSuggestIndex = new SuggestIndex() ;
            albumIntroSuggestIndex.setId(UUID.randomUUID().toString().replace("-" , ""));
            albumIntroSuggestIndex.setTitle( albumInfoIndex.getAlbumIntro() );
            albumIntroSuggestIndex.setKeyword(new Completion(new String[]{ albumInfoIndex.getAlbumIntro() }));
            albumIntroSuggestIndex.setKeywordPinyin(new Completion(new String[]{ PinYinUtils.toHanyuPinyin(albumInfoIndex.getAlbumIntro()) } ));
            albumIntroSuggestIndex.setKeywordSequence(new Completion(new String[]{ PinYinUtils.getFirstLetter( albumInfoIndex.getAlbumIntro()) } ));
            suggestIndexRepository.save(albumIntroSuggestIndex) ;

            // 专辑的主播名称
            SuggestIndex announcerNameSuggestIndex = new SuggestIndex() ;
            announcerNameSuggestIndex.setId(UUID.randomUUID().toString().replace("-" , ""));
            announcerNameSuggestIndex.setTitle( albumInfoIndex.getAnnouncerName() );
            announcerNameSuggestIndex.setKeyword(new Completion(new String[]{ albumInfoIndex.getAnnouncerName() }));
            announcerNameSuggestIndex.setKeywordPinyin(new Completion(new String[]{ PinYinUtils.toHanyuPinyin(albumInfoIndex.getAnnouncerName()) } ));
            announcerNameSuggestIndex.setKeywordSequence(new Completion(new String[]{ PinYinUtils.getFirstLetter( albumInfoIndex.getAnnouncerName()) } ));
            suggestIndexRepository.save(announcerNameSuggestIndex) ;

        }

    /**
     * @description: 下架专辑
     * @author Petrichor
     * @date 2023/12/27 19:00
     * @param albumId 专辑id
     * @return NULL
     **/
    @Override
    public void lowerAlbum(Long albumId) {
        // 从ES中删除对应的专辑
        albumIndexRepository.deleteById(albumId);

        // 创建一个新的布隆过滤器
        RBloomFilter<Object> newBloomFilter = redissonClient.getBloomFilter(RedisConstant.ALBUM_BLOOM_FILTER_NEW);
        newBloomFilter.tryInit(1000000 , 0.00001) ;

        // 从ES中获取专辑id
        albumIndexRepository.findAll().forEach(albumInfoIndex -> newBloomFilter.add(albumInfoIndex.getId()));

        // 定义lua脚本
        String script = "redis.call(\"del\" , KEYS[1])\n" +
                "redis.call(\"del\" , \"{\"..KEYS[1]..\"}:\"..\"config\")\n" +
                "redis.call(\"rename\" , KEYS[2] , KEYS[1])\n" +
                "redis.call(\"rename\" , \"{\"..KEYS[2]..\"}:\"..\"config\" , \"{\"..KEYS[1]..\"}:\"..\"config\")\n" +
                "return 1" ;

        // 执行lua脚本
        redisTemplate.execute(new DefaultRedisScript<>(script , Long.class) , Arrays.asList(RedisConstant.ALBUM_BLOOM_FILTER , RedisConstant.ALBUM_BLOOM_FILTER_NEW)) ;

        // 打印日志
        log.info("reset bloomFilter成功了............");
    }

    /**
     * @description: 检索功能实现 当点击搜索按钮
     * @author Petrichor
     * @date 2023/12/27 18:43
     * @param albumIndexQuery 专辑信息搜索
     * @return albumSearchResponseVo 检索出来的商品信息
     **/
    @Override
    @SneakyThrows
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        // 调用方法获取SearchRequest对象
        SearchRequest searchRequest = buildSearchRequest(albumIndexQuery);
        // 调用ElasticsearchClient对象的search方法实现搜素
        SearchResponse<AlbumInfoIndex> search = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        // 解析结果搜索结果对象，得到响应结果数据
        AlbumSearchResponseVo albumSearchResponseVo = parseSearchResult(search , albumIndexQuery) ;
        return albumSearchResponseVo;
    }

    /**
     * @description: 获取SearchRequest对象
     * @author Petrichor
     * @date 2023/12/27 18:43
     * @param albumIndexQuery 专辑信息搜索
     * @return SearchRequest 获取SearchRequest对象
     **/
    private SearchRequest buildSearchRequest(AlbumIndexQuery albumIndexQuery) {
        // 创建一个BoolQuery对象 TODO:builder.bool
        // BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();

        // 多字段搜索
        String keyword = albumIndexQuery.getKeyword();
        if (!StrUtil.isEmpty(keyword)){
            boolQueryBuilder.must(QueryBuilders.multiMatch(builder -> builder.query(keyword).fields("albumTitle" , "albumIntro")));
        }
        // 分类搜索条件
        // category 1
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id !=null ){
            boolQueryBuilder.filter(QueryBuilders.term(builder -> builder.field("category1Id").value(category1Id)));
        }
        // category 2
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id !=null ){
            boolQueryBuilder.filter(QueryBuilders.term(builder -> builder.field("category2Id").value(category2Id)));
        }
        // category 3
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id !=null ){
            boolQueryBuilder.filter(QueryBuilders.term(builder -> builder.field("category3Id").value(category3Id)));
        }
        // 专辑属性的搜素条件
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)){
            // 属性id:属性值id
            for(String attributeInfoAndValue : attributeList) {
                String[] attrIdAndValueIdArr = attributeInfoAndValue.split(":");
                Long attrId = Long.parseLong(attrIdAndValueIdArr[0]);
                Long valueId = Long.parseLong(attrIdAndValueIdArr[1]);
                BoolQuery boolQuery = QueryBuilders.bool()
                        .must(QueryBuilders.term(builder -> builder.field("attributeValueIndexList.attributeId").value(attrId)))
                        .must(QueryBuilders.term(builder -> builder.field("attributeValueIndexList.valueId").value(valueId)))
                        .build();
                NestedQuery nestedQuery = new NestedQuery.Builder()
                        .path("attributeValueIndexList")
                        .query(boolQuery._toQuery())
                        .build();
                Query query = new Query.Builder().nested(nestedQuery).build();
                boolQueryBuilder.filter(query) ;
            }
        }
        // 创建一个Query对象
        Query query = new Query.Builder().bool(boolQueryBuilder.build()).build();
        // 常见一个SearchRequest对象然后进行返回 TODO:searchRequest
        SearchRequest.Builder searchRequest = new SearchRequest.Builder();
        // 设置分页搜索条件
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        searchRequest.from((pageNo - 1) * pageSize).size(pageSize) ;

        // 设置排序参数
        FieldSort fieldSort = new FieldSort.Builder().field("hotScore").order(SortOrder.Desc).build() ;
        String order = albumIndexQuery.getOrder();
        if (!StrUtil.isEmpty(order)) {
            String[] split = order.split(":");
            String field = split[0] ;
            switch (field) {
                case "1" :
                    fieldSort = new FieldSort.Builder()
                            .field("hotScore")
                            .order(split[1].equalsIgnoreCase("desc") ? SortOrder.Desc : SortOrder.Asc )
                            .build() ;
                    break;
                case "2" :
                    fieldSort = new FieldSort.Builder()
                            .field("playStatNum")
                            .order(split[1].equalsIgnoreCase("desc") ? SortOrder.Desc : SortOrder.Asc )
                            .build() ;
                    break;

                case "3" :
                    fieldSort = new FieldSort.Builder()
                            .field("createTime")
                            .order(split[1].equalsIgnoreCase("desc") ? SortOrder.Desc : SortOrder.Asc )
                            .build() ;
                    break;
            }
        }
        SortOptions.Builder sortOptionsBuilder = new SortOptions.Builder();
        sortOptionsBuilder.field(fieldSort) ;
        searchRequest.sort(sortOptionsBuilder.build());

        // 设置高亮的参数
        if(!StrUtil.isEmpty(keyword)) {
            Map<String , HighlightField> highlightFieldMap = new HashMap<>() ;
            highlightFieldMap.put("albumTitle" , new HighlightField.Builder().preTags("<font color='red'>").postTags("</font>").build()) ;
            highlightFieldMap.put("albumIntro" , new HighlightField.Builder().preTags("<font color='red'>").postTags("</font>").build()) ;
            Highlight highlight = new Highlight.Builder()
                    .fields(highlightFieldMap)
                    .build();
            searchRequest.highlight(highlight) ;
        }
        // TODO: 根据传过来的参数动态设置搜索参数
        searchRequest.query(query).index("albuminfo");

        SearchRequest build = searchRequest.build();
        System.out.println(build.toString());

        // 返回
        return build;

    }
    /**
     * @description: 解析结果搜索结果对象，得到响应结果数据
     * @author Petrichor
     * @date 2023/12/27 19:30
     * @param search 搜索结果对象
     * @param albumIndexQuery 不一定用
     * @return albumSearchResponseVo 检索出来的商品信息
    **/
    private AlbumSearchResponseVo parseSearchResult(SearchResponse<AlbumInfoIndex> search, AlbumIndexQuery albumIndexQuery) {
        // 获取搜索的数据的元数据对象
        HitsMetadata<AlbumInfoIndex> hitsMetadata = search.hits();
        // 常见AlbumSearchResponseVo对象，封装数据
        AlbumSearchResponseVo albumSearchResponseVo = new  AlbumSearchResponseVo() ;
        List<Hit<AlbumInfoIndex>> hitList = hitsMetadata.hits();
        List<AlbumInfoIndexVo> albumInfoIndexVoList = new ArrayList<>() ;
        for(Hit<AlbumInfoIndex> hit : hitList) {
            AlbumInfoIndex albumInfoIndex = hit.source();
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo() ;
            BeanUtils.copyProperties(albumInfoIndex , albumInfoIndexVo);

            // 获取高亮的结果
            Map<String, List<String>> highlight = hit.highlight();
            if(!CollectionUtils.isEmpty(highlight)) {
                List<String> albumTitleList = highlight.get("albumTitle");
                if(!CollectionUtils.isEmpty(albumTitleList)) {
                    albumInfoIndexVo.setAlbumTitle(albumTitleList.get(0));
                }
                List<String> albumIntroList = highlight.get("albumIntro");
                if(!CollectionUtils.isEmpty(albumIntroList)) {
                    albumInfoIndexVo.setAlbumIntro(albumIntroList.get(0));
                }
            }

            albumInfoIndexVoList.add(albumInfoIndexVo) ;
        }
        albumSearchResponseVo.setList(albumInfoIndexVoList);

        // 解析响应结果，将数据封装到AlbumSearchResponseVo对象中
        long total = hitsMetadata.total().value();
        albumSearchResponseVo.setTotal(total);
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        int totalPage = PageUtil.totalPage(total, albumIndexQuery.getPageSize());
        albumSearchResponseVo.setTotalPages(Long.valueOf(totalPage));

        // 返回数据
        return albumSearchResponseVo ;
    }

    /**
     * @description: 根据一级分类id查询专辑数据
     * @author Petrichor
     * @date 2023/12/27 22:45
     * @param category1Id  一级分类id
     * @return List<AlbumInfoIndexSearchVo> 专辑数据集合
     **/
    @Override
    @SneakyThrows
    public List<AlbumInfoIndexSearchVo> channel(Long category1Id) {
        // TODO bool
        // 创建一个BoolQuery对象
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
        // 根据一级分类的id查询置顶的三级分类数据
        Result<List<BaseCategory3>> result = categoryFeignClient.findTopBaseCategory3(category1Id);
        List<BaseCategory3> baseCategory3List = result.getData();
        Map<Long, BaseCategory3> category3Map = baseCategory3List
                .stream()
                .collect(Collectors.toMap(baseCategory3 -> baseCategory3.getId(), baseCategory3 -> baseCategory3));

        boolQueryBuilder.must(QueryBuilders.term(builder -> builder.field("category1Id").value(category1Id)));
        List<FieldValue> fieldValueList = baseCategory3List.stream().map(baseCategory3 -> FieldValue.of(baseCategory3.getId())).collect(Collectors.toList());
        TermsQueryField termsQueryField = new TermsQueryField.Builder()
                .value(fieldValueList)
                .build();
        boolQueryBuilder.must(QueryBuilders.terms(builder -> builder.field("category3Id").terms(termsQueryField)));

        // TODO source
        SourceConfig sourceConfig = new SourceConfig.Builder().fetch(false).build();

        // TODO aggs
        TermsAggregation termsAggregation = AggregationBuilders.terms()
                .field("category3Id")
                .size(10)
                .build();
        FieldSort fieldSort = new FieldSort.Builder()
                .field("hotScore")
                .order(SortOrder.Desc)
                .build();
        SortOptions sortOptions = new SortOptions.Builder()
                .field(fieldSort)
                .build();
        TopHitsAggregation topHitsAggregation = AggregationBuilders.topHits()
                .size(6)
                .sort(sortOptions)
                .build();
        Aggregation topAlbumInfoAgg = new Aggregation.Builder()
                .topHits(topHitsAggregation)
                .build();
        Aggregation aggregation = new Aggregation.Builder()
                .terms(termsAggregation)
                .aggregations("topAlbumInfoAgg",topAlbumInfoAgg)
                .build();

        // 创建一个Query对象
        Query query = new Query.Builder().bool(boolQueryBuilder.build()).build();
        // 常见一个SearchRequest对象然后进行返回 TODO:searchRequest
        SearchRequest searchRequest = new SearchRequest.Builder()
                .query(query)
                .aggregations("category3IdAgg" , aggregation)
                .source(sourceConfig)
                .build();

        // 发送搜索请求
        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        // 解析响应结果
        Map<String, Aggregate> aggregations = searchResponse.aggregations();
        Aggregate category3IdAgg = aggregations.get("category3IdAgg");
        LongTermsAggregate longTermsAggregate = (LongTermsAggregate)category3IdAgg._get();
        Buckets<LongTermsBucket> longTermsBucketBuckets = longTermsAggregate.buckets();
        List<LongTermsBucket> longTermsBuckets = longTermsBucketBuckets.array();


        List<AlbumInfoIndexSearchVo> albumInfoIndexSearchVos = new ArrayList<>();
        for(LongTermsBucket bucket : longTermsBuckets) {

            long baseCategory3Id = bucket.key();
            Map<String, Aggregate> subAggMap = bucket.aggregations();
            Aggregate aggregate = subAggMap.get("topAlbumInfoAgg");
            TopHitsAggregate topHitsAggregate = (TopHitsAggregate) aggregate._get() ;
            HitsMetadata<JsonData> hitsMetadata = topHitsAggregate.hits();
            List<Hit<JsonData>> hitList = hitsMetadata.hits();

            // 获取到了专辑数据
            List<AlbumInfoIndex> albumInfoIndexList = hitList.stream().map(hit -> {
                String jsonData = hit.source().toString();
                AlbumInfoIndex albumInfoIndex = JSON.parseObject(jsonData, AlbumInfoIndex.class);
                return albumInfoIndex;
            }).collect(Collectors.toList());

            // 封装数据
            AlbumInfoIndexSearchVo albumInfoIndexSearchVo = new AlbumInfoIndexSearchVo() ;
            albumInfoIndexSearchVo.setBaseCategory3(category3Map.get(baseCategory3Id));
            albumInfoIndexSearchVo.setList(albumInfoIndexList);
            albumInfoIndexSearchVos.add(albumInfoIndexSearchVo) ;

        }

        // 返回
        return albumInfoIndexSearchVos;
    }
    /**
     * @description: 关键字自动补全
     * @author Petrichor
     * @date 2023/12/28 21:04
     * @param keyword 关键字
     * @return
     **/
    @Override
    @SneakyThrows
    public List<String> completeSuggest(String keyword) {
        // 调用方法获取completeSuggestSearchRequest对象
        SearchRequest searchRequest = buildCompleteSuggestSearchRequest(keyword);
        // 发送搜索请求
        SearchResponse<SuggestIndex> searchResponse = elasticsearchClient.search(searchRequest, SuggestIndex.class);
        // 解析结果搜索结果对象，得到parseCompleteSuggestSearchResult响应结果数据
        List<String> stringList = parseCompleteSuggestSearchResult(searchResponse , keyword) ;
        return stringList;
    }
    /**
     * @description: 更新排行榜数据
     * @author Petrichor
     * @date 2024/1/2 16:46
     **/
    @Override
    @SneakyThrows
    public void updateLatelyAlbumRanking() {

        // 2.创建一个Query对象
        Query query = new Query.Builder().matchAll(QueryBuilders.matchAll().build()).build();
        // 3.创建一个sourceConfig
        SourceConfig sourceConfig = new SourceConfig.Builder().fetch(false).build();
        // 5.创建一个TermsAggregation
        TermsAggregation termsAggregation = new TermsAggregation.Builder()
                .field("category1Id")
                .size(100)
                .build();
        // 4.创建一个Aggregation
        Aggregation.Builder.ContainerBuilder containerBuilder = new Aggregation.Builder()
                .terms(termsAggregation);
        String[] rankingDimensionArray = { "hotScore" , "playStatNum" , "subscribeStatNum" , "buyStatNum" , "commentStatNum" };
        for (String rankingDimension  : rankingDimensionArray){
            System.out.println(rankingDimension);
            // 5>创建 fieldSort
            FieldSort fieldSort = new FieldSort.Builder()
                    .field(rankingDimension)
                    .order(SortOrder.Desc)
                    .build();
            // 4>创建 SortOptions
            SortOptions sortOptions = new SortOptions.Builder()
                    .field(fieldSort)
                    .build();
            // 3>创建 TopHitsAggregation
            TopHitsAggregation topHitsAggregation = new TopHitsAggregation.Builder()
                    .size(10)
                    .sort(sortOptions)
                    .build();
            // 2>创建 Aggregation
            Aggregation aggregationBuilder = new Aggregation.Builder()
                    .topHits(topHitsAggregation)
                    .build();
            // 1>aggregations key value
            containerBuilder.aggregations(rankingDimension+"TopHitsAgg",aggregationBuilder);
        }
        // 1.常见一个SearchRequest对象然后进行返回 TODO:searchRequest
        SearchRequest searchRequest = new SearchRequest.Builder()
                .query(query)
                .source(sourceConfig)
                .aggregations("category1IdAgg", containerBuilder.build())
                .build();
        // 发送搜索请求
        SearchResponse<AlbumInfoIndex> searchResponse = elasticsearchClient.search(searchRequest,AlbumInfoIndex.class);
        Map<String, Aggregate> aggregations = searchResponse.aggregations();
        Aggregate aggregate = aggregations.get("category1IdAgg");
        LongTermsAggregate longTermsAggregate = (LongTermsAggregate) aggregate._get();
        Buckets<LongTermsBucket> buckets = longTermsAggregate.buckets();
        List<LongTermsBucket> bucketList = buckets.array();
        System.out.println(bucketList);
        for(LongTermsBucket bucket : bucketList) {
            // 获取一级分类的id
            long category1Id = bucket.key();

            Map<String, Aggregate> aggregateMap = bucket.aggregations();

            for(String rankingDimension : rankingDimensionArray) {

                Aggregate topHitsAgg = aggregateMap.get( rankingDimension + "TopHitsAgg");
                TopHitsAggregate topHitsAggregate = (TopHitsAggregate) topHitsAgg._get() ;
                HitsMetadata<JsonData> hitsMetadata = topHitsAggregate.hits();
                List<Hit<JsonData>> hits = hitsMetadata.hits();
                List<AlbumInfoIndex> infoIndexList = hits.stream().map(hit -> {
                    String json = hit.source().toJson().toString();
                    AlbumInfoIndex albumInfoIndex = JSON.parseObject(json, AlbumInfoIndex.class);
                    return albumInfoIndex;
                }).collect(Collectors.toList());

                /**
                 * 使用Redis中的hash结构完成数据的存储
                 */
                String redisKey = RedisConstant.RANKING_KEY_PREFIX + category1Id ;
                redisTemplate.opsForHash().put(redisKey , rankingDimension, JSON.toJSONString(infoIndexList));

            }
        }
    }
    /**
     * @description: 获取排行榜列表
     * @author Petrichor
     * @date 2024/1/2 19:16
     * @param category1Id 一级分类id
     * @param dimension 热度:hotScore、播放量:playStatNum、订阅量:subscribeStatNum、购买量:buyStatNum、评论数:albumCommentStatNum
     * @return List<AlbumInfoIndexVo> 返回专辑列表
     **/
    @Override
    public List<AlbumInfoIndexVo> findRankingList(Long category1Id, String dimension) {
        String redisKey = RedisConstant.RANKING_KEY_PREFIX +category1Id;
        // 排行榜 集合数据的json格式
        Object obj = redisTemplate.opsForHash().get(redisKey, dimension);
        if (obj!=null){{
            List<AlbumInfoIndex> albumInfoIndices = JSON.parseArray(obj.toString(), AlbumInfoIndex.class);
            List<AlbumInfoIndexVo> albumInfoIndexVoList = albumInfoIndices.stream().map(albumInfoIndex -> {
                AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
                BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
                return albumInfoIndexVo;
            }).collect(Collectors.toList());
            return albumInfoIndexVoList ;
        }}
        return null;
    }
    /**
     * @description: 用于[service.service-album]/com/atguigu/tingshu/album/canal/CanalClientListener.java
     *               的远程调用,更新统计数据
     * @author Petrichor
     * @date 2024/1/5 19:26
     * @param albumId 专辑id
     * @param statType 统计类型：0401-播放量 0402-订阅量 0403-购买量 0403-评论数
     * @param statNum 统计数目
     * @return NULL
     **/
    @Override
    public void updateAlbumStat(Long albumId, String statType, Integer statNum) {
        // 根据albumId查询专辑数据
        Optional<AlbumInfoIndex> optional = albumIndexRepository.findById(albumId);
        AlbumInfoIndex albumInfoIndex = optional.get();

        // 更新指定的统计数据值
        switch (statType) {
            case SystemConstant.ALBUM_STAT_PLAY:
                albumInfoIndex.setPlayStatNum(statNum);
                break;
            case SystemConstant.ALBUM_STAT_SUBSCRIBE:
                albumInfoIndex.setSubscribeStatNum(statNum);
                break;
            case SystemConstant.ALBUM_STAT_BROWSE:
                albumInfoIndex.setBuyStatNum(statNum);
                break;
            case SystemConstant.ALBUM_STAT_COMMENT:
                albumInfoIndex.setCommentStatNum(statNum);
                break;
        }
        albumIndexRepository.save(albumInfoIndex);
    }

    /**
     * @description: 解析结果搜索结果对象，得到parseCompleteSuggestSearchResult响应结果数据
     * @author Petrichor
     * @date 2023/12/28 21:22
     * @param searchResponse 响应结果
     * @param keyword 关键字
     * @return
    **/
    private List<String> parseCompleteSuggestSearchResult(SearchResponse<SuggestIndex> searchResponse, String keyword) {
        // 创建List集合对象，封装响应结果数据
        List<String> suggestResult = new ArrayList<>() ;

        // 解析结果
        Map<String, List<Suggestion<SuggestIndex>>> suggestMap = searchResponse.suggest();

        // 获取keyword提示词结果
        suggestMap.get("suggestionKeyword").forEach(suggestion -> {
            CompletionSuggest<SuggestIndex> completion = suggestion.completion();
            completion.options().forEach(option -> suggestResult.add(option.source().getTitle()));
        });

        // 获取keywordPinyin提示词结果
        suggestMap.get("suggestionkeywordPinyin").forEach(suggestion -> {
            CompletionSuggest<SuggestIndex> completion = suggestion.completion();
            completion.options().forEach(option -> suggestResult.add(option.source().getTitle()));
        });

        // 获取keywordSequence提示词结果
        suggestMap.get("suggestionkeywordSequence").forEach(suggestion -> {
            CompletionSuggest<SuggestIndex> completion = suggestion.completion();
            completion.options().forEach(option -> suggestResult.add(option.source().getTitle()));
        });

        // 返回
        return suggestResult ;
    }

    /**
     * @description:  关键字自动补全 == 调用方法获取completeSuggestSearchRequest对象
     * @author Petrichor
     * @date 2023/12/28 21:21
     * @param keyword 关键词
     * @return completeSuggestSearchRequest对象
    **/
    private SearchRequest buildCompleteSuggestSearchRequest(String keyword) {
        FieldSuggester keyWordFieldSuggester = new FieldSuggester.Builder().prefix(keyword).completion(new CompletionSuggester.Builder()
                .field("keyword")
                .skipDuplicates(true)
                .size(10)
                .fuzzy(new SuggestFuzziness.Builder().fuzziness("auto").build())
                .build()).build();
        FieldSuggester keywordPinyinSuggester = new FieldSuggester.Builder().prefix(keyword).completion(new CompletionSuggester.Builder()
                .field("keywordPinyin")
                .skipDuplicates(true)
                .size(10)
                .fuzzy(new SuggestFuzziness.Builder().fuzziness("auto").build())
                .build()).build();
        FieldSuggester keywordSequenceSuggester = new FieldSuggester.Builder().prefix(keyword).completion(new CompletionSuggester.Builder()
                .field("keywordSequence")
                .skipDuplicates(true)
                .size(10)
                .fuzzy(new SuggestFuzziness.Builder().fuzziness("auto").build())
                .build()).build();
        Suggester suggester = new Suggester.Builder()
                .suggesters("suggestionKeyword", keyWordFieldSuggester)
                .suggesters("suggestionkeywordPinyin" , keywordPinyinSuggester)
                .suggesters("suggestionkeywordSequence" , keywordSequenceSuggester)
                .build();
        SearchRequest searchRequest = new SearchRequest.Builder()
                .index("suggestinfo")
                .suggest(suggester)
                .build() ;
        return searchRequest;
    }
}
